package org.ihelpuoo.parser;

/**
 * <b>StringTraveler class:</b>
 * <hr color=grey size=1>
 * <b>overview</b>
 * <p>
 * Provide a basic Traveler for Parser class to get higher performance
 * in parsing.
 * <p>
 * Writing a complex Parser, we always create lots of object by reading
 * the Source (generally, source is a string). we will call those objects
 * as <em>tiny parser</em>. Each <em>tiny parser</em> will process the
 * same source object. If the source is string, StringTraveler can help
 * parser to get higher perfermance.
 * <p>
 * StringTraveler class will keep a string instance as char[], and mentain
 * a index as the point.
 * <br>
 * The point can <u>only be moved to next</u>, it will force you Parser class
 * can not move back the point, as we known move point back will cause the bad 
 * performance during parsing.
 * <p>
 * <b>Begin and End</b>
 * <p>
 * This class has a private attribute -- <em>chars</em>. its is a <em>char[]</em>,
 * cursor orignal position is -1, last position(<i>when done for the traveling</i>)
 * is <em>chars.length</em>.
 * <br>
 * If cursor equal <em>chars.length</em>, isEnd() will return true.
 *
 * @author zozoh  created @ 2006
 *
 */
public class StringTraveler
{
	public static final char	EMPTY_CHAR	= '\0';

	protected char[]			chars;
	protected int				index;

	/**
	 * This is the only way to create a instance. You must give content
	 * 
	 * @param content: String object
	 */
	public StringTraveler(String content)
	{
		chars = (content == null ? new char[0] : content.toCharArray());
		index = -1;
	}

	public StringTraveler(char[] content)
	{
		chars = (content == null ? new char[0] : content);
		index = -1;
	}

	/**
	 * Get next char from current position. Before call this method, you
	 * better call isEnd() to test cursor is move to end or not, else maybe
	 * out rang exception will be rised.
	 * <p>
	 * If out of rang, '\0'(EMPTY_CHAR) will be returned.
	 * 
	 * @return next char 
	 */
	public char next()
	{
		try
		{
			return chars[++index];
		}
		catch (Exception e)
		{
			index = chars.length;
			return EMPTY_CHAR;
		}
	}

	/**
	 * Move cursor forward to special position. If the position is out of rang
	 * '\0' (EMPTY_CHAR) will be returned.
	 * 
	 * @param offset: the offset. next(0) is mean get current position
	 * @return
	 */
	public char next(int offset)
	{
		try
		{
			return chars[index += offset];
		}
		catch (Exception e)
		{
			index = chars.length;
			return EMPTY_CHAR;
		}
	}

	/**
	 * move to next position by special character. 
	 * 
	 * @param c: special char
	 * @return all content from current position to the special position (exclusive)
	 * That mean
	 * <p>
	 * if string is "456ux87"
	 * <p>
	 * call <i>next('u')</i>, it will return "456", and <i>current()</i> is 'u';
	 * <br>
	 * If call <i>next('x')</i> continue, it will return <i>null</i>.
	 * <br>
	 * And call <i>next('7')</i>, it will return "8".
	 */
	public String next(char c)
	{
		int oldIndex = index;
		StringBuffer sb = new StringBuffer();
		while (!isEnd())
		{
			if (next() == c)
				return (sb.length() == 0 ? null : sb.toString());
			sb.append(current());
		}
		index = oldIndex;
		return null;
	}

	public char current()
	{
		try
		{
			return chars[index];
		}
		catch (Exception e)
		{
			return EMPTY_CHAR;
		}
	}

	/**
	 * @return string length
	 */
	public int length()
	{
		return chars.length;
	}

	/**
	 * @return cursor is pointing to the end or not.
	 */
	public boolean isEnd()
	{
		return index >= chars.length;
	}

	/**
	 * This function will campare the <i>next chars</i> with the param.
	 * <p>
	 * <i>next chars</i> mean: the sub char array from cursor+1 to cursor+str.length
	 * <p>
	 * If isEnd() is true, the function always return <em>false</em>.
	 * <p>
	 * @param str: the string will to test. if NULL, return false directly.
	 * @return: next chars is match the str or not
	 */
	public boolean hasNext(String str)
	{
		if (str == null)
			return false;
		return hasNext(str.toCharArray());
	}

	/**
	 * @see hasNext(String str)
	 */
	public boolean hasNext(char[] charArr)
	{
		if (null == charArr)
			return false;
		if (charArr.length == 0)
			return false;
		int offset = index + 1;
		if ((chars.length - offset) < charArr.length)
			return false;
		for (int i = 0; i < charArr.length; i++)
		{
			if (chars[i + offset] != charArr[i])
				return false;
		}
		return true;
	}

	/**
	 * @see hasNext(String str)
	 */
	public boolean hasNext(char c)
	{
		try
		{
			return chars[index + 1] == c;
		}
		catch (RuntimeException e)
		{
			return false;
		}
	}

	/**
	 * @return content
	 */
	@Override
	public String toString()
	{
		return String.valueOf(chars);
	}

	/**
	 * @return a string from current postion to end.
	 */
	public String remain()
	{
		StringBuffer sb = new StringBuffer();
		for (index++; index < chars.length; index++)
		{
			sb.append(chars[index]);
		}
		return sb.toString();
	}

	/**
	 * Move cursor to -1
	 */
	public void restoreCursor()
	{
		index = -1;
	}
}
